iT邦幫忙

2023 iThome 鐵人賽

DAY 11
0
Vue.js

淺談vue3源碼,很淺的那種系列 第 11

[Day 11] runtime-dom——封裝操作dom元素的方法 - 1

  • 分享至 

  • xImage
  •  

「♪♪♪♪♪♪♪♪♪♪♪♪♪♪」——莫札特

上回書說道,我們定義了虛擬dom節點的資料結構,接下來我們就寫一個函數,將虛擬dom渲染成真實dom。

為此,我們需要封裝一些對dom元素操作的方法,先建立/src/runtime-dom/nodeOps.ts:

export const nodeOps = {
  insert(child: HTMLElement | Text, parent: HTMLElement, anchor: HTMLElement | Text = null) {
    parent.insertBefore(child, anchor)
  },
  remove(child: HTMLElement | Text) {
    const parentNode = child.parentNode
    if (parentNode) parentNode.removeChild(child)
  },
  setElementText(el: HTMLElement, text: string) {
    el.textContent = text
  },
  setText(node: HTMLElement | Text, text: string) {
    node.nodeValue = text
  },
  querySelector(selector: string) {
    return document.querySelector(selector)
  },
  parentNode(node: HTMLElement) {
    return node.parentNode
  },
  nextSibling(node: HTMLElement) {
    return node.nextSibling
  },
  createElement(tagName: string) {
    return document.createElement(tagName)
  },
  createText(text: string) {
    return document.createTextNode(text)
  }
}

能學習vue3源碼的小夥伴,大多應該都跟我一樣,很久沒碰dom元素了吧?就讓我們來挨個兒複習一下。

insert(child: HTMLElement | Text, parent: HTMLElement, anchor: HTMLElement | Text = null) {
  parent.insertBefore(child, anchor);
}

HTMLElement的insertBefore方法可以往自己裡面插入一個新的dom元素節點,那個節點會被插入至anchor前面,如果anchor是null,則插入至最後面。因此insert方法就是往parent裡面,anchor的前方或最後面插入child的方法。

remove、setElementText、querySelector、parentNode、createElement都挺見明知意的,跳過不講。

setText(node: HTMLElement | Text, text: string) {
  node.nodeValue = text;
}

nodeValue和textContent、innerText的共通點是都是取節點裡面的文字,差別在於nodeValue只取文字節點裡的文字。比方說如果我們有個div標籤:

<div id="node">text</div>

<script>
const node = document.querySelector('#node')
console.log(node.nodeValue) // null
console.log(node.childNodes[0].nodeValue) // text
</script>

我們可以看到只有node.childNodes[0]這個div裡的文字節點,能取到非null的nodeValue。
至於textContent和innerText的差別就比較小的,比方說如果你用css:text-transform:uppercase;把文字改變成大寫,innerText會回傳大寫,textContent會回傳被css影響前原本的內容,所以這裡統一用textContent代替innerText可能更合適一些。

nextSibling(node: HTMLElement) {
  return node.nextSibling;
}

HTMLElement的nextSibling屬性回傳同層級的下一個dom元素。

createText(text: string) {
  return document.createTextNode(text);
}

創建文本節點,就是上面提到有nodeValue的那個文本節點。

有了這些方法,我們之後便能針對接收到的虛擬dom去將真實dom的節點一一渲染出來。但能把節點渲染出來當然還不夠,我們還必須要能夠解析這些d虛擬dom上的class、style、甚至是@click等事件,因此我們還要新建以下檔案:

  1. /src/runtime-dom/patchProp.ts
  2. /src/runtime-dom/modules/attr.ts
  3. /src/runtime-dom/modules/class.ts
  4. /src/runtime-dom/modules/event.ts
  5. /src/runtime-dom/modules/style.ts

並在patchProp.ts寫下以下代碼:

import { patchAttr } from "./modules/attr";
import { patchClass } from "./modules/class";
import { patchEvent } from "./modules/event";
import { patchStyle } from "./modules/style";

export function patchProp(el: HTMLElement, key: string, prevValue: any, nextValue: any) {
  if (key === 'class') patchClass()
  else if (key === 'style') patchStyle()
  else if (/^\@/.test(key)) patchEvent()
  else patchAttr()
}

從目前的代碼便可看出,這個patchProp會針對接收到的key,調用對應的方法,去給我們之後渲染完的dom元素加上class、style或綁定事件等等。明天我們將會去一一完成這些給渲染好的dom元素加上屬性或事件的方法,為之後渲染dom元素做準備。

githubmain分支commit「[Day 11] runtime-dom——封裝操作dom元素的方法 - 1」


上一篇
[Day 10] 虛擬dom
下一篇
[Day 12] runtime-dom——封裝操作dom元素的方法 - 2
系列文
淺談vue3源碼,很淺的那種31
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言